package com.itesm.distribuidos.tarea4;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;

public class MatrixServerPeticion extends Thread {
	private MatrixOperations matrixOperations; //Una instancia de la clase Matrix Operations
	private ServerSocket socket;
	private Socket connection;
	private long ioTime;
	private long processingTime;
	private String response;

	public MatrixServerPeticion()
	{
		matrixOperations = new MatrixOperations();
		try {
			socket = new ServerSocket(61000);
		} catch (IOException e) { System.out.println("IO Exception creating the socket"); }
	}

	/**
	 * Metodo run del Thread
	 */
	public void run()
	{
		System.out.println("MatrixServerPeticion started...");
		while(true) //Iniciamos el ciclo
		{
			try {
				connection = socket.accept();

				//Leer los datos de entrada del cliente
				ObjectInputStream ois = new ObjectInputStream(connection.getInputStream());
				Integer[] requestData = (Integer[]) ois.readObject();
				int hilo = requestData[2];
				int size = requestData[1];
				int operation = requestData[0];
				
				System.out.println("Received a request for a " + size + "x" + size + "matrix  Thread#"+hilo);
				
				//Preparamos stream para mandar una respuesta
				ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
				out.flush();

				//Generamos las matrices aleatoriamente
				long initTime = System.currentTimeMillis();
				String[][] a = matrixOperations.createRandomStringMatrix(size);
				String[][] b = matrixOperations.createRandomStringMatrix(size);
				processingTime = System.currentTimeMillis() - initTime;

				//Escribimos las matrices generadas en un archivo de texto
				initTime = System.currentTimeMillis();
				createInputFiles(a,b,Integer.toString(size));
				ioTime = System.currentTimeMillis() - initTime;

				//Liberamos memoria e imprimos informacion que recibio el server
				a = null;
				b = null;
				response = operation + "%" + size + "%";

				//Declaramos una nueva matriz para guardar los resultados de las operaciones
				String[][] matrix = null;
				initTime = 0l;

				//Leemos los archivos de entrada para hacer las operaciones
				initTime = System.currentTimeMillis();
				String[][] matrix1 = loadMatrixFromFile("matrix1_" + size + ".txt", size);
				String[][] matrix2 = loadMatrixFromFile("matrix2_" + size + ".txt", size);
				ioTime += System.currentTimeMillis() - initTime;

				//Realizamos la operacion y medimos el tiempo
				switch(operation)
				{
				case 1:
					initTime = System.currentTimeMillis();
					matrix = matrixOperations.addition(matrix1, matrix2);
					processingTime += System.currentTimeMillis()-initTime;
					response += "Suma[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
					break;
				case 2:
					initTime = System.currentTimeMillis();
					matrix = matrixOperations.substraction(matrix1, matrix2);
					processingTime += System.currentTimeMillis()-initTime;
					response += "Resta[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
					break;
				case 3:
					initTime = System.currentTimeMillis();
					matrix = matrixOperations.multiplication(matrix1, matrix2);
					processingTime += System.currentTimeMillis()-initTime;
					response += "Multiplicacion[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
					break;
				case 4:
					initTime = System.currentTimeMillis();
					matrix = matrixOperations.division(matrix1, matrix2);
					processingTime += System.currentTimeMillis()-initTime;
					response += "Division[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
					break;
				}

				//Creamos el archivo con el resultado
				initTime = System.currentTimeMillis();
				this.createOutputFile(matrix, Integer.toString(size));
				ioTime += System.currentTimeMillis() - initTime;
				
				//Leemos la respuesta del archivo de resultado
				initTime = System.currentTimeMillis();
				String matrixResult = this.loadMatrixFromFileToSendResult("result_" + size + ".txt", size);
				ioTime += System.currentTimeMillis() - initTime;
				
				//Imprimimos totales de tiempo de E/S y Procesamiento
				response += "%" + ioTime + "%";
				response += processingTime + "%";
				response += matrixResult;
				
				//Eviamos la respuesta al cliente
				//System.out.println(response);
				out.writeObject(response);
				
				//Cerramos todos los streams y conexion
				out.close();
				ois.close();
				connection.close();
			}
			catch (IOException e) { }
			catch (ClassNotFoundException e) { }
		}
	}

	/**
	 * Carga un archivo de texto en una matriz de Strings
	 * @param matrixFile 	path del archivo de texto a abrir
	 * @param size			tamaño de la matriz a llenar
	 * @return				matriz de Strings
	 */
	public String[][] loadMatrixFromFile(String matrixFile, int size){
		String[][] stringMatrix = new String[size][size];
		try
		{
			FileInputStream fstream = new FileInputStream(matrixFile);
			
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			String strLine;
			StringTokenizer tokenizer;
			
			int i = 0;
			while ((strLine = br.readLine()) != null)
			{
				tokenizer = new StringTokenizer(strLine, ",");
				if(tokenizer.countTokens() == size)
				{
					for (int j=0; j<size; j++)
						stringMatrix[i][j] = tokenizer.nextToken();
				}
				i++;
			}
			
			br.close();
			in.close();
		}
		catch(IOException ioe) { }
		return stringMatrix;
	}
	
	/**
	 * Carga un archivo de texto en una matriz de Strings
	 * @param matrixFile 	path del archivo de texto a abrir
	 * @param size			tamaño de la matriz a llenar
	 * @return				matriz de Strings
	 */
	public String loadMatrixFromFileToSendResult(String matrixFile, int size){
		StringBuffer stringMatrix = new StringBuffer();
		try
		{
			FileInputStream fstream = new FileInputStream(matrixFile);
			
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			String strLine;
			stringMatrix.append("[");
			while ((strLine = br.readLine()) != null)
			{
				stringMatrix.append("["+ strLine + "]");
			}
			stringMatrix.append("]");
			
			br.close();
			in.close();
		}
		catch(IOException ioe) { }
		return stringMatrix.toString();
	}
	
	/**
	 * Escribe la matriz resultado en un archivo de texto
	 * @param a			La matriz resultado	
	 * @param suffix	Un sufijo para el archivo
	 */
	public void createOutputFile(String[][] a, String suffix)
	{
		try {
			FileOutputStream fos = new FileOutputStream("result_"+ suffix +".txt"); 
			OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<a.length; i++)
			{
				for(int j=0; j<a.length; j++)
				{
					if(j>0)
						out.write(",");
					out.write(a[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
		}
		catch (UnsupportedEncodingException e) { }
		catch (FileNotFoundException e) { }
		catch (IOException e) { }
	}
	
	/**
	 * Escribe las matrices en archivos de texto como matrix1 y matrix2 
	 * @param a			la matriz 1
	 * @param b			la matriz 2
	 * @param suffix	Un sufijo para identificar los archivos
	 */
	public void createInputFiles(String[][] a, String[][] b, String suffix)
	{
		try {
			FileOutputStream fos = new FileOutputStream("matrix1_" + suffix + ".txt"); 
			OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<a.length; i++)
			{
				for(int j=0; j<a.length; j++)
				{
					if(j>0)
						out.write(",");
					out.write(a[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
			
			fos = new FileOutputStream("matrix2_" + suffix + ".txt"); 
			out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<b.length; i++)
			{
				for(int j=0; j<b.length; j++)
				{
					if(j>0)
						out.write(",");
					out.write(b[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
		}
		catch (UnsupportedEncodingException e) { }
		catch (FileNotFoundException e) { }
		catch (IOException e) { }
	}

	public static void main(String...arg)
	{
		new MatrixServerPeticion().start();
	}
}
